﻿namespace VsxFactory.Modeling.StrategyManager.Strategies
{
    using EnvDTE;
    using EnvDTE80;
    using System;
    using System.Diagnostics;
    using System.Threading;

    internal class CodeTypesObserver : IDisposable
    {
        private CodeModelEvents modelEvents;
        private LastAction lastAction = LastAction.None;
        private object lastElement = null;
        private object lastParent = null;

        public event AttributeAdded OnAttributeAdded;
        public event AttributeChanged OnAttributeChanged;
        public event AttributeRemoved OnAttributeRemoved;
        public event BaseTypeChanged OnBaseTypeChanged;
        public event ElementPropertiesChanged OnElementPropertiesChanged;
        public event FieldAdded OnFieldAdded;
        public event MemberRemoved OnFieldRemoved;
        public event FieldRenamed OnFieldRenamed;
        public event MethodAdded OnMethodAdded;
        public event MemberRemoved OnMethodRemoved;
        public event MethodRenamed OnMethodRenamed;
        public event NestedTypeAdded OnNestedTypeAdded;
        public event ParameterAdded OnParameterAdded;
        public event ParameterRemoved OnParameterRemoved;
        public event ParameterRenamed OnParameterRenamed;
        public event PropertyAdded OnPropertyAdded;
        public event MemberRemoved OnPropertyRemoved;
        public event TypeAdded OnTypeAdded;
        public event TypeRemoved OnTypeRemoved;
        public event TypeRenamed OnTypeRenamed;

        public CodeTypesObserver(IServiceProvider serviceProvier)
        {
            DTE2 dte = serviceProvier.GetService(typeof(DTE)) as DTE2;
            this.modelEvents = ((Events2) dte.Events).get_CodeModelEvents(null);
            this.modelEvents.ElementAdded += this.ModelEvents_ElementAdded;
            this.modelEvents.ElementDeleted += this.ModelEvents_ElementDeleted;
            this.modelEvents.ElementChanged += this.ModelEvents_ElementChanged;
        }

        #region Destruction
        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if ((this.modelEvents != null) && disposing)
            {
                this.modelEvents.ElementAdded -= this.ModelEvents_ElementAdded;
                this.modelEvents.ElementDeleted -= this.ModelEvents_ElementDeleted;
                this.modelEvents.ElementChanged -= this.ModelEvents_ElementChanged;
                this.modelEvents = null;
            }
        }

        ~CodeTypesObserver()
        {
            this.Dispose(false);
            GC.SuppressFinalize(this);
        }
        #endregion

        private void ModelEvents_ElementAdded(CodeElement element)
        {
            Debug.WriteLine(string.Format("Type {0} added", element.FullName));

            if ((this.lastAction != LastAction.Add) || (this.lastElement != element))
            {
                this.lastAction = LastAction.Add;
                this.lastElement = element;
                if (element is CodeType)
                {
                    CodeType codeType = element as CodeType;
                    if (codeType.Parent is CodeNamespace)
                    {
                        if (this.OnTypeAdded != null)
                        {
                            this.OnTypeAdded(codeType);
                        }
                    }
                    else if ((codeType.Parent is CodeClass) && (this.OnNestedTypeAdded != null))
                    {
                        this.OnNestedTypeAdded(codeType.Parent as CodeClass, codeType);
                    }
                }
                else
                {
                    CodeType parent;
                    if (element is CodeVariable)
                    {
                        CodeVariable field = element as CodeVariable;
                        parent = field.Parent as CodeType;
                        if (this.OnFieldAdded != null)
                        {
                            this.OnFieldAdded(parent, field);
                        }
                    }
                    else if (element is CodeProperty)
                    {
                        CodeProperty property = element as CodeProperty;
                        parent = property.Parent as CodeType;
                        if (this.OnPropertyAdded != null)
                        {
                            this.OnPropertyAdded(parent, property);
                        }
                    }
                    else
                    {
                        CodeFunction2 m;
                        if (element is CodeFunction2)
                        {
                            m = element as CodeFunction2;
                            parent = m.Parent as CodeType;
                            if (this.OnMethodAdded != null)
                            {
                                this.OnMethodAdded(parent, m);
                            }
                        }
                        else if (element is CodeParameter)
                        {
                            CodeParameter p = element as CodeParameter;
                            m = p.Parent as CodeFunction2;
                            parent = m.Parent as CodeType;
                            if (this.OnParameterAdded != null)
                            {
                                this.OnParameterAdded(m, p);
                            }
                            else if (this.OnElementPropertiesChanged != null)
                            {
                                this.OnElementPropertiesChanged(m as CodeElement);
                            }
                        }
                        else if (element is CodeAttribute2)
                        {
                            CodeAttribute2 attribute = element as CodeAttribute2;
                            if (this.OnAttributeAdded != null)
                            {
                                this.OnAttributeAdded(attribute);
                            }
                        }
                    }
                }
            }
        }

        private void ModelEvents_ElementChanged(CodeElement element, vsCMChangeKind Change)
        {
            CodeType codeType;
            Debug.WriteLine(string.Format("Type {0} changed", element.Name));
            if (Change <= vsCMChangeKind.vsCMChangeKindTypeRefChange)
            {
                switch (Change)
                {
                    case vsCMChangeKind.vsCMChangeKindRename:
                        if (!(element is CodeType))
                        {
                            if (element is CodeParameter)
                            {
                                CodeParameter parameter = element as CodeParameter;
                                CodeFunction2 function = parameter.Parent as CodeFunction2;
                                int index = 0;
                                foreach (CodeParameter p in function.Parameters)
                                {
                                    if (p.Name == parameter.Name)
                                    {
                                        if (this.OnParameterRenamed != null)
                                        {
                                            this.OnParameterRenamed(function, index, parameter);
                                        }
                                        break;
                                    }
                                    index++;
                                }
                            }
                            else if (element is CodeNamespace)
                            {
                                CodeNamespace ns = element as CodeNamespace;
                            }
                            else
                            {
                                CodeType type;
                                if (element is CodeVariable)
                                {
                                    CodeVariable field = element as CodeVariable;
                                    type = field.Parent as CodeType;
                                    if (this.OnFieldRenamed != null)
                                    {
                                        this.OnFieldRenamed(type, field);
                                    }
                                }
                                else if (element is CodeFunction2)
                                {
                                    CodeFunction2 m = element as CodeFunction2;
                                    type = m.Parent as CodeType;
                                    if (this.OnMethodRenamed != null)
                                    {
                                        this.OnMethodRenamed(type, m);
                                    }
                                }
                            }
                            return;
                        }
                        codeType = element as CodeType;
                        if (this.OnTypeRenamed != null)
                        {
                            this.OnTypeRenamed(codeType.FullName, codeType);
                        }
                        return;

                    case vsCMChangeKind.vsCMChangeKindUnknown:
                    case vsCMChangeKind.vsCMChangeKindSignatureChange:
                    case vsCMChangeKind.vsCMChangeKindTypeRefChange:
                        if (this.OnElementPropertiesChanged != null)
                        {
                            this.OnElementPropertiesChanged(element);
                        }
                        return;

                    case (vsCMChangeKind.vsCMChangeKindUnknown | vsCMChangeKind.vsCMChangeKindRename):
                        goto Label_02DE;
                }
            }
            else
            {
                if (Change != vsCMChangeKind.vsCMChangeKindBaseChange)
                {
                    if (Change != vsCMChangeKind.vsCMChangeKindArgumentChange)
                    {
                        goto Label_02DE;
                    }
                    if (element is CodeAttribute2)
                    {
                        CodeAttribute2 attribute = element as CodeAttribute2;
                        if (this.OnAttributeChanged != null)
                        {
                            this.OnAttributeChanged(attribute);
                        }
                    }
                }
                else if (element is CodeType)
                {
                    codeType = element as CodeType;
                    if (((codeType.Bases.Count > 0) && (codeType.Bases.Item(1) is CodeType)) && (this.OnBaseTypeChanged != null))
                    {
                        this.OnBaseTypeChanged(element as CodeType);
                    }
                }
                return;
            }

        Label_02DE:
            if (this.OnElementPropertiesChanged != null)
            {
                this.OnElementPropertiesChanged(element);
            }
        }

        private void ModelEvents_ElementDeleted(object Parent, CodeElement element)
        {
            Debug.WriteLine(string.Format("Type {0} deleted", element.Name));
            if (((this.lastAction != LastAction.Delete) || (this.lastParent != Parent)) || (this.lastElement != element))
            {
                string fullname;
                this.lastParent = Parent;
                this.lastElement = element;
                this.lastAction = LastAction.Delete;
                if (element.IsCodeType)
                {
                    CodeType codeType = element as CodeType;
                    CodeElement parent = Parent as CodeElement;
                    if (parent == null)
                    {
                        fullname = codeType.Name;
                    }
                    else
                    {
                        fullname = parent.FullName + "." + codeType.Name;
                    }
                    if (this.OnTypeRemoved != null)
                    {
                        this.OnTypeRemoved(codeType, fullname);
                    }
                }
                else
                {
                    CodeType parent;
                    if (element is CodeVariable)
                    {
                        CodeVariable field = element as CodeVariable;
                        parent = Parent as CodeType;
                        if (this.OnFieldRemoved != null)
                        {
                            this.OnFieldRemoved(parent, element.Name);
                        }
                    }
                    else if (element is CodeProperty)
                    {
                        CodeProperty property = element as CodeProperty;
                        parent = Parent as CodeType;
                        if (this.OnPropertyRemoved != null)
                        {
                            this.OnPropertyRemoved(parent, element.Name);
                        }
                    }
                    else
                    {
                        CodeFunction2 m;
                        if (element is CodeFunction2)
                        {
                            m = element as CodeFunction2;
                            parent = Parent as CodeType;
                            if (this.OnMethodRemoved != null)
                            {
                                this.OnMethodRemoved(parent, m.get_Prototype(0x40));
                            }
                        }
                        else if (element is CodeParameter)
                        {
                            CodeParameter p = element as CodeParameter;
                            m = Parent as CodeFunction2;
                            parent = m.Parent as CodeType;
                            if (this.OnParameterRemoved != null)
                            {
                                this.OnParameterRemoved(m, element.Name);
                            }
                        }
                        else if (element is CodeNamespace)
                        {
                            if (Parent is CodeElement)
                            {
                                fullname = (Parent as CodeElement).FullName + "." + element.Name + ".";
                            }
                            else
                            {
                                fullname = element.Name + ".";
                            }
                        }
                        else if ((element is CodeAttribute2) && (this.OnAttributeRemoved != null))
                        {
                            this.OnAttributeRemoved(Parent as CodeElement, element.Name);
                        }
                    }
                }
            }
        }

        private enum LastAction
        {
            None,
            Add,
            Delete,
            Change
        }
    }
}

